package org.lc.gateway.config;

import cn.hutool.core.util.ArrayUtil;
import lombok.RequiredArgsConstructor;
import org.lc.gateway.auth.AuthorizationManager;
import org.lc.gateway.handler.AuthServerAccessDeniedHandler;
import org.lc.gateway.handler.AuthServerAuthenticationEntryPoint;
import org.lc.gateway.util.JwtTokenUtil;
import org.lc.platform.base.constant.AuthConstant;
import org.lc.platform.oauth2.properties.SecurityOauth2Properties;
import org.lc.platform.redis.component.RedisPublisher;
import org.lc.platform.redis.service.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;

import java.text.ParseException;

/**
 * 权限配置
 * 注解需要使用@EnableWebFluxSecurity而非@EnableWebSecurity,因为SpringCloud Gateway基于WebFlux
 *
 * @author lc
 */
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Configuration
@EnableWebFluxSecurity
public class MultiWebSecurityConfig {

    private final CacheService cacheService;
    private final AuthServerAccessDeniedHandler authServerAccessDeniedHandler;
    private final AuthServerAuthenticationEntryPoint authServerAuthenticationEntryPoint;
    private final SecurityOauth2Properties authProperties;


    /**
     * 路由转发权限配置
     *
     * @param http {}
     * @return {}
     */
    @Bean

    @RefreshScope
    SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http, ReactiveJwtDecoder jwtDecoder) {

        http.oauth2ResourceServer().jwt()
                .jwtAuthenticationConverter(jwtAuthenticationConverter());

        /* 自定义处理JWT请求头过期或签名错误的结果 */
        http.oauth2ResourceServer().authenticationEntryPoint(authServerAuthenticationEntryPoint);
        var httpAuth = http.authorizeExchange();
        if (!authProperties.getWhiteUrls().isEmpty()) {
            httpAuth = httpAuth.pathMatchers(ArrayUtil.toArray(authProperties.getWhiteUrls(), String.class)).permitAll();
        }
        httpAuth.anyExchange().access(new AuthorizationManager(cacheService, jwtDecoder, authProperties))
                .and()
                .exceptionHandling()
                /* 处理未授权 */
                .accessDeniedHandler(authServerAccessDeniedHandler)
                /* 处理未认证 */
                .authenticationEntryPoint(authServerAuthenticationEntryPoint)
                .and()
                .cors()
                .and().csrf().disable();

        return http.build();
    }

    @Bean
    public ReactiveJwtDecoder jwtDecoder() {
        try {
            var jwk_set = cacheService.getStr(AuthConstant.JWT_SET);
            return JwtTokenUtil.getJwtDecoder(jwk_set);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * ServerHttpSecurity没有将jwt中authorities的负载部分当做Authentication，需要把jwt的Claim中的authorities加入
     * 解决方案：重新定义ReactiveAuthenticationManager权限管理器，默认转换器JwtGrantedAuthoritiesConverter
     */
    @Bean
    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
    }
}
